package coc.core;

import java.util.Date;
import java.util.Map.Entry;
import java.util.concurrent.Callable;

import org.jsoup.nodes.Document;

import coc.core.collector.Collector;
import coc.core.collector.CollectorFactory;
import coc.core.collector.CollectorType;
import coc.core.collector.HtmlUnitCollector;
import coc.core.url.Url;
import bsh.EvalError;
import bsh.Interpreter;
import coo.base.exception.UncheckedException;

/**
 * 基于BeanShell的通用蜘蛛。
 */
public class Spider implements Callable<SpiderResult> {
	private Interpreter interpreter = new Interpreter();
	private Url url;
	private CrawlerConfig config;
	private SpiderResult result;
	private Collector collector;
	private Document document;

	/**
	 * 构造方法。
	 * 
	 * @param url
	 *            待处理的链接
	 * @param config
	 *            爬虫配置
	 */
	public Spider(Url url, CrawlerConfig config) {
		this.url = url;
		setVariable(VariableNames.URL, url);
		setVariable(VariableNames.NOW, new Date());

		this.config = config;
		setVariable(VariableNames.CFG, config);

		result = new SpiderResult();
		result.setTaskId(config.getTaskId());
		result.setTaskName(config.getTaskName());
		result.setUrl(url);
		setVariable(VariableNames.RES, result);

		collector = createCollector();
		setVariable(VariableNames.COR, collector);
	}

	@Override
	public SpiderResult call() throws Exception {
		try {
			init();
			interpreter.eval(config.getScript());
			return result;
		} finally {
			destroy();
		}
	}

	/**
	 * 初始化。
	 */
	private void init() {
		// 如果待处理的URL不为空则加载并解析页面
		if (!url.isNullUrl()) {
			collector.collect(url);
			// 如果是HTMLUNIT采集器，在执行采集页面动作后等待一定的时间，使得页面加载完整
			if (collector instanceof HtmlUnitCollector) {
				sleep(config.getWaitForGetPage());
			}
			document = collector.createDocument();
			setVariable(VariableNames.DOC, document);
			setVariable(VariableNames.CTN, document);
		}
	}

	/**
	 * 清理。
	 */
	private void destroy() {
		if (collector != null) {
			collector.destroy();
		}
	}

	/**
	 * 创建采集器。
	 * 
	 * @return 返回采集器。
	 */
	private Collector createCollector() {
		CollectorType collectorType = config.getCollectorType();
		// 如果满足采集器条件设置，则取条件设置的采集器类型
		for (Entry<String, String> entry : config.getCollectorTerms()
				.entrySet()) {
			Boolean result = false;
			try {
				result = (Boolean) interpreter.eval(entry.getKey());
			} catch (EvalError e) {
				throw new UncheckedException(e);
			}
			if (result) {
				collectorType = CollectorType.valueOf(entry.getValue());
			}
		}
		return CollectorFactory.createCollector(collectorType,
				config.getTimeout(), config.getCookies(),
				config.getBrowserVersion());
	}

	/**
	 * 设置变量。
	 * 
	 * @param name
	 *            变量名
	 * @param value
	 *            变量值
	 */
	private void setVariable(String name, Object value) {
		try {
			interpreter.set(name, value);
		} catch (EvalError e) {
			throw new UncheckedException(e);
		}
	}

	/**
	 * 线程休眠。
	 * 
	 * @param millis
	 *            休眠毫秒数。
	 */
	private void sleep(int millis) {
		try {
			Thread.sleep(millis);
		} catch (InterruptedException e) {
			throw new UncheckedException("蜘蛛线程休眠时发生异常。");
		}
	}

	/**
	 * 变量名常量类。脚本中可以通过这些变量名来引用对应的对象。
	 */
	private class VariableNames {
		/** 当前处理的链接 */
		public static final String URL = "$url";
		/** 爬虫配置 */
		public static final String CFG = "$cfg";
		/** 蜘蛛结果 */
		public static final String RES = "$res";
		/** 采集器 */
		public static final String COR = "$cor";
		/** 文档对象 */
		public static final String DOC = "$doc";
		/** 当前容器 */
		public static final String CTN = "$ctn";
		/** 当前时间 */
		public static final String NOW = "$now";
	}
}
